﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace MkvSplicer.Video
{
    public abstract partial class VideoTrack
    {
        public delegate int UpdateProgress(Int64 Current, Int64 Total, IntPtr ICPrivate);
        protected static class Ffms2
        {
            private static bool initialized;

            private static byte[] ConvertToUTF8(string input)
            {
                return System.Text.Encoding.UTF8.GetBytes(input + '\0');
            }

            private static string ConvertFromUTF8(FFMS_ErrorInfo input)
            {
                byte[] raw = new byte[input.BufferSize];
                Marshal.Copy(input.Buffer, raw, 0, input.BufferSize);
                return System.Text.Encoding.UTF8.GetString(raw);
            }

            public static void index(string source, out int bitdepth,
                out string colourspace, out VideoFrame[] frames, out VideoFrame[] gops,
                UpdateProgress updateCallback, IntPtr ICPrivate)
            {
                if (!Ffms2.initialized)
                {
                    FFMS_Init(0, 1);
                    FFMS_SetLogLevel(0);
                    initialized = true;
                }
                FFMS_ErrorInfo error = new FFMS_ErrorInfo(1024);

                //Create the index
                IntPtr index = FFMS_MakeIndex(ConvertToUTF8(source), 0, 0, IntPtr.Zero, IntPtr.Zero,
                    0, updateCallback, ICPrivate, ref error);
                if (index == IntPtr.Zero)
                {
                    throw new Ffms2Exception(ConvertFromUTF8(error), error.ErrorType);
                }

                //Get track number of type FFMS_TYPE_VIDEO
                int videoTrackNumber = FFMS_GetFirstIndexedTrackOfType(index, 0, ref error);
                if (videoTrackNumber < 0)
                {
                    throw new Ffms2Exception(ConvertFromUTF8(error), error.ErrorType);
                }

                //Create the video source
                IntPtr videoSource = FFMS_CreateVideoSource(ConvertToUTF8(source),
                    videoTrackNumber, index, 1, 1, ref error);
                if (videoSource == IntPtr.Zero)
                {
                    throw new Ffms2Exception(ConvertFromUTF8(error), error.ErrorType);
                }

                //Once created, the index is no longer needed
                FFMS_DestroyIndex(index);

                //Get colourspace and bitdepth info based on the first frame.
                IntPtr framePtr = FFMS_GetFrame(videoSource, 0, ref error);
                if (framePtr == IntPtr.Zero)
                {
                    throw new Ffms2Exception(ConvertFromUTF8(error), error.ErrorType);
                }

                FFMS_Frame frame = (FFMS_Frame)Marshal.PtrToStructure(framePtr, typeof(FFMS_Frame));
                switch ((AVPixelFormat)frame.EncodedPixelFormat)
                {
                    case AVPixelFormat.YUV422P:
                        colourspace = "i422";
                        bitdepth = 8;
                        break;
                    case AVPixelFormat.YUV444P:
                        colourspace = "i444";
                        bitdepth = 8;
                        break;
                    case AVPixelFormat.YUV420P10:
                        colourspace = "i420";
                        bitdepth = 10;
                        break;
                    case AVPixelFormat.YUV422P10:
                        colourspace = "i422";
                        bitdepth = 10;
                        break;
                    case AVPixelFormat.YUV444P10:
                        colourspace = "i444";
                        bitdepth = 10;
                        break;
                    default:
                        colourspace = "i420";
                        bitdepth = 8;
                        break;
                }

                //Get video track
                IntPtr videoTrack = FFMS_GetTrackFromVideo(videoSource);
                IntPtr timeBasePtr = FFMS_GetTimeBase(videoTrack);
                FFMS_TrackTimeBase timeBase = (FFMS_TrackTimeBase)Marshal.PtrToStructure(
                    timeBasePtr, typeof(FFMS_TrackTimeBase));

                //Get keyframe data
                int nFrames = FFMS_GetNumFrames(videoTrack);
                int nGops = -1;
                List<VideoFrame> _gops = new List<VideoFrame>();
                frames = new VideoFrame[nFrames];

                for (int i = 0; i < nFrames; i++)
                {
                    IntPtr frameInfoPtr = FFMS_GetFrameInfo(videoTrack, i);
                    FFMS_FrameInfo frameInfo = (FFMS_FrameInfo)Marshal.PtrToStructure(
                        frameInfoPtr, typeof(FFMS_FrameInfo));
                    long timecode = (long)((frameInfo.PTS * timeBase.Num) / (double)timeBase.Den);
                    if (frameInfo.Keyframe != 0)
                        nGops++;
                    frames[i] = new VideoFrame(timecode, i, nGops, frameInfo.Keyframe != 0);
                    if (frameInfo.Keyframe != 0)
                        _gops.Add(frames[i]);
                }
                gops = _gops.ToArray();

                //Cleanup
                Marshal.FreeHGlobal(error.Buffer);
                FFMS_DestroyVideoSource(videoSource);
            }

            [Serializable()]
            public class Ffms2Exception : System.Exception
            {
                private int errorCode;
                public bool Cancelled {get { return errorCode == 10; } } //FFMS_ERROR_CANCELLED

                public Ffms2Exception() : base() { }
                public Ffms2Exception(string message, int errorCode)
                    : base(message)
                {
                    this.errorCode = errorCode;
                }
                public Ffms2Exception(string message, System.Exception inner) : base(message, inner) { }

                // A constructor is needed for serialization when an 
                // exception propagates from a remoting server to the client.  
                protected Ffms2Exception(System.Runtime.Serialization.SerializationInfo info,
                    System.Runtime.Serialization.StreamingContext context) { }
            }

            #region interops
            [StructLayout(LayoutKind.Sequential)]
            private struct FFMS_ErrorInfo
            {
                public int ErrorType;
                public int SubType;
                public int BufferSize;
                public IntPtr Buffer;

                public FFMS_ErrorInfo(int size)
                {   //MUST deallocate buffer once finished!
                    ErrorType = 0;
                    SubType = 0;
                    BufferSize = size;
                    Buffer = Marshal.AllocHGlobal(size);
                }
            };

#pragma warning disable 0649 //Disable 'never assigned to' warning
            private struct FFMS_TrackTimeBase //Marshal.PtrToStructure
            {
                public long Num;
                public long Den;
            };
            private struct FFMS_FrameInfo //Marshal.PtrToStructure
            {
                public long PTS;
                public int RepeatPict;
                public int Keyframe;
            };
            private struct FFMS_Frame
            {
                [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
                public IntPtr[] Data;
                [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
                public int[] Linesize;
                public int EncodedWidth;
                public int EncodedHeight;
                public int EncodedPixelFormat;
                public int ScaledWidth;
                public int ScaledHeight;
                public int ConvertedPixelFormat;
                public int KeyFrame;
                public int RepeatPict;
                public int InterlacedFrame;
                public int TopFieldFirst;
                public char PictType;
                public int ColorSpace;
                public int ColorRange;
            };
#pragma warning restore 0649

            private enum AVPixelFormat
            { //Subset of video formats that I think H.264 supports
                YUV420P = 0,
                YUV422P = 4,
                YUV444P = 5,
                YUV420P10 = 72,
                YUV422P10 = 74,
                YUV444P10 = 78
            };

            [DllImport(FilePaths.ffms2_relative, SetLastError = false, CharSet = CharSet.Ansi)]
            public static extern void FFMS_Init(int CPUFeatures, int UseUTF8Paths);

            [DllImport(FilePaths.ffms2_relative, SetLastError = false, CharSet = CharSet.Ansi)]
            private static extern void FFMS_SetLogLevel(int Level);

            [DllImport(FilePaths.ffms2_relative, SetLastError = false, CharSet = CharSet.Ansi)]
            private static extern IntPtr FFMS_MakeIndex(byte[] SourceFile, int IndexMask, int DumpMask,
                IntPtr TAudioNameCallback, IntPtr ANCPrivate, int ErrorHandling,
                UpdateProgress TIndexCallback, IntPtr ICPrivate, ref FFMS_ErrorInfo ErrorInfo);

            [DllImport(FilePaths.ffms2_relative, SetLastError = false, CharSet = CharSet.Ansi)]
            private static extern void FFMS_DestroyIndex(IntPtr Index);

            [DllImport(FilePaths.ffms2_relative, SetLastError = false, CharSet = CharSet.Ansi)]
            private static extern int FFMS_GetFirstIndexedTrackOfType(IntPtr Index, int TrackType, ref FFMS_ErrorInfo ErrorInfo);

            [DllImport(FilePaths.ffms2_relative, SetLastError = false, CharSet = CharSet.Ansi)]
            private static extern IntPtr FFMS_CreateVideoSource(byte[] SourceFile, int Track, IntPtr Index,
                int Threads, int SeekMode, ref FFMS_ErrorInfo ErrorInfo);

            [DllImport(FilePaths.ffms2_relative, SetLastError = false, CharSet = CharSet.Ansi)]
            private static extern IntPtr FFMS_GetTrackFromVideo(IntPtr VideoSource);

            [DllImport(FilePaths.ffms2_relative, SetLastError = false, CharSet = CharSet.Ansi)]
            private static extern int FFMS_GetNumFrames(IntPtr Track);

            [DllImport(FilePaths.ffms2_relative, SetLastError = false, CharSet = CharSet.Ansi)]
            private static extern IntPtr FFMS_GetTimeBase(IntPtr Track);

            [DllImport(FilePaths.ffms2_relative, SetLastError = false, CharSet = CharSet.Ansi)]
            private static extern IntPtr FFMS_GetFrameInfo(IntPtr Track, int Frame);

            [DllImport(FilePaths.ffms2_relative, SetLastError = false, CharSet = CharSet.Ansi)]
            private static extern IntPtr FFMS_GetFrame(IntPtr VideoSource, int n, ref FFMS_ErrorInfo ErrorInfo);

            [DllImport(FilePaths.ffms2_relative, SetLastError = false, CharSet = CharSet.Ansi)]
            private static extern void FFMS_DestroyVideoSource(IntPtr VideoSource);
            #endregion

        }
    }
}
